﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Web;
using System.Net;
using System.Data;
using System.Diagnostics;
using System.Threading;
using System.Linq;
using System.Xml.Linq;

namespace ListItemEditor
{
    public class ProgressEventArgs : EventArgs
    {
        public int CurrentItem { get; set; }
        public int TotalItems { get; set; }
        public bool Success { get; set; }
        public string FileName { get; set; }
        public string ErrorMessage { get; set; }

        public ProgressEventArgs(int currentItem, int totalItems, bool success, string fileName, string errorMessage)
        {
            this.CurrentItem = currentItem;
            this.TotalItems = totalItems;
            this.Success = success;
            this.FileName = fileName;
            this.ErrorMessage = errorMessage;
        }
    }

    public class MultipleDownloadFileHelper
    {
        public event EventHandler<ProgressEventArgs> FileDownloading;
        public event EventHandler<ProgressEventArgs> FileDownloaded;

        public class SPObj
        {
            public bool IsFolder { get; set; }
            public string Url { get; set; }
        }

        public void DownloadMultipleFiles(ICredentials credentials, string destFolder, bool recursive, params SPObj[] sourceObjs)
        {
            if (sourceObjs.Length == 0) return;

            ThreadStart threadStart = () =>
            {
                int count = 0; int totalCount = sourceObjs.Length;

                Action<DAVHelper.SaveFileRecursiveData> callback = (DAVHelper.SaveFileRecursiveData o) =>
                    {
                        ProgressEventArgs args = new ProgressEventArgs(count, totalCount, true, o.ItemUrl, null);
                        if (!o.Complete && FileDownloading != null) FileDownloading(this, args);
                        if (o.Complete)
                        {
                            if (o.IsFolder) totalCount += o.FolderItemsCount;
                            if (o.Exception != null)
                            {
                                args.Success = false;
                                args.ErrorMessage = o.Exception.Message;
                            }
                            if (FileDownloaded != null) FileDownloaded(this, args);
                            count++;
                        }
                    };

                foreach (SPObj spObj in sourceObjs)
                {
                    if (spObj.IsFolder) DAVHelper.SaveFilesRecursivelyCallback(spObj.Url, Path.Combine(destFolder, Path.GetFileName(spObj.Url)), credentials, callback, recursive);
                    else DAVHelper.SaveFileToDiskCallback(spObj.Url, destFolder, credentials, callback);
                }
            };

            Thread thread = new Thread(threadStart);
            thread.Start();
        }
    }

    public class MultipleUploadFileHelper
    {
        public event EventHandler<ProgressEventArgs> FileUploading;
        public event EventHandler<ProgressEventArgs> FileUploaded;

        private class IOObj
        {
            public bool IsFolder { get; set; }
            public string Path { get; set; }
        }

        public void UploadMultipleFiles(ICredentials credentials, string destFolderUrl, params string[] sourcePaths)
        {
            if (sourcePaths.Length == 0) return;
            string rootSourceFolder = Path.GetDirectoryName(sourcePaths[0]);

            ThreadStart threadStart = () =>
                {
                    Dictionary<string, HashSet<string>> _spFolderMap = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
                    List<IOObj> ioObjs = this.GetIOObjsFromSourcePaths(sourcePaths);
                    int count = 0; int totalCount = ioObjs.Count;

                    foreach (IOObj ioObj in ioObjs)
                    {
                        string sourcePath = ioObj.Path;
                        ProgressEventArgs args = new ProgressEventArgs(count, totalCount, true, sourcePath, null);
                        if (FileUploading != null) FileUploading(this, args);
                        try
                        {
                            string rootRelPath = sourcePath.Substring(rootSourceFolder.Length).Trim('\\');
                            string destPath = destFolderUrl.TrimEnd('/') + "/" + rootRelPath.Replace("\\", "/");

                            if (ioObj.IsFolder)
                            {
                                // check to see if the folder already exists
                                string parentDestPath = destPath.Substring(0, destPath.LastIndexOf('/'));
                                if (!_spFolderMap.ContainsKey(parentDestPath)) _spFolderMap[parentDestPath] = new HashSet<string>(DAVHelper.GetFileObj(parentDestPath, credentials).Where(fo => fo.IsFolder).Select(fo => fo.Name).Distinct(StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase);

                                if (!_spFolderMap[parentDestPath].Contains(Path.GetFileName(sourcePath)))
                                    DAVHelper.CreateSharePointFolder(destPath, credentials);
                            }
                            else DAVHelper.UploadFile(destPath, sourcePath, credentials);
                        }
                        catch (Exception ex)
                        {
                            args.Success = false;
                            args.ErrorMessage = ex.Message;
                        }
                        if (FileUploaded != null) FileUploaded(this, args);
                        count++;
                    }
                };

            Thread thread = new Thread(threadStart);
            thread.Start();
        }

        private List<IOObj> GetIOObjsFromSourcePaths(string[] sourcePaths)
        {
            List<IOObj> ioObjs = new List<IOObj>();
            foreach (string sourcePath in sourcePaths)
            {
                if (Directory.Exists(sourcePath))
                {
                    IOObj folder = new IOObj() {  IsFolder = true, Path = sourcePath };
                    ioObjs.Add(folder);
                    AddIOObjsRecursive (folder, ioObjs);
                }
                else ioObjs.Add (new IOObj() { Path = sourcePath});
            }
            return ioObjs;
        }

        private void AddIOObjsRecursive(IOObj parentFolder, List<IOObj> objs)
        {
            foreach (string f in Directory.GetDirectories(parentFolder.Path))
            {
                IOObj folder = new IOObj() {  IsFolder = true, Path = f };
                objs.Add(folder);
                AddIOObjsRecursive(folder, objs);
            }

            objs.AddRange(Directory.GetFiles(parentFolder.Path)
                .Select(f => new IOObj() {  Path = f }));
        }
    }

    // taken with some modifications from here - http://blogs.msdn.com/rohitpuri/archive/2007/04/10/upload-download-file-to-from-wss-document-library-using-dav.aspx
    public static class DAVHelper
    {
        public class FileObj
        {
            public string Url { get; set; }
            public string Name { get; set; }
            public bool IsFolder { get; set; }
            public int ContentLength { get; set; }
        }

        public static void SaveFileToDisk(string fileUrl, string path, ICredentials credentials)
        {
            Console.WriteLine("Saving " + fileUrl + "  to " + path);

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(fileUrl);
            request.Method = "GET";
            request.UserAgent = @"Microsoft-WebDAV-MiniRedir/6.1.7600";
            request.Headers.Add("translate", "f");
            request.Credentials = credentials == null ? CredentialCache.DefaultCredentials : credentials;

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                string fileName = Path.GetFileName(fileUrl);
                SafeStreamToFile(response, fileName, path);
            }
        }

        public static void UploadFile(string destUrl, string sourcePath, ICredentials credentials)
        {
            try
            {
                Uri destUri = new Uri(destUrl);
                if (!File.Exists(sourcePath)) throw new FileNotFoundException("File not found.", sourcePath);

                WebRequest req = WebRequest.Create(destUri);
                req.Method = "PUT";
                req.Headers.Add("Overwrite", "F");
                req.Timeout = System.Threading.Timeout.Infinite;
                req.Credentials = credentials == null ? CredentialCache.DefaultCredentials : credentials;

                using (FileStream inStream = File.OpenRead(sourcePath))
                {
                    using (Stream outStream = req.GetRequestStream())
                    {
                        CopyStream(inStream, outStream);
                    }
                }

                using (WebResponse ores = req.GetResponse())
                {
                    using (Stream responseStream = ores.GetResponseStream())
                    {
                        using (StreamReader sr = new StreamReader(responseStream))
                        {
                            sr.ReadToEnd();
                        }
                    }
                }
            }
            catch (WebException)
            {
                throw;
            }
            catch (System.Exception)
            {
                throw;
            }
        }

        public class SaveFileRecursiveData
        {
            public int FolderItemsCount { get; set; }
            public string ItemUrl { get; set; }
            public string DestPath { get; set; }
            public bool IsFolder { get; set; }
            public Exception Exception { get; set; }
            public bool Complete { get; set; }
        }

        public static void SaveFilesRecursivelyCallback(string startUrl, string destFolder, ICredentials credentials, Action<SaveFileRecursiveData> callback, bool recursive)
        {
            SaveFileRecursiveData callbackData = new SaveFileRecursiveData() { IsFolder = true, ItemUrl = startUrl, DestPath = destFolder };
            if (callback != null) callback(callbackData);

            FileObj[] objs = null;
            try
            {
                if (!Directory.Exists(destFolder)) Directory.CreateDirectory(destFolder);
                if (recursive)
                {
                    objs = GetFileObj(startUrl, credentials);
                    callbackData.FolderItemsCount = objs.Length;
                }
            }
            catch (Exception ex) { callbackData.Exception = ex; }

            callbackData.Complete = true;
            if (callback != null) callback(callbackData);
            if (objs == null) return;

            foreach (FileObj fileObj in objs)
            {
                string objUrl = startUrl.TrimEnd('/') + "/" + fileObj.Name;
                if (!fileObj.IsFolder)
                {
                    SaveFileToDiskCallback(objUrl, destFolder, credentials, callback);
                }
                else
                {
                    SaveFilesRecursivelyCallback(objUrl, Path.Combine(destFolder, fileObj.Name), credentials, callback, recursive);
                }
            }
        }

        public static void SaveFileToDiskCallback(string objUrl, string destFolder, ICredentials credentials, Action<SaveFileRecursiveData> callback)
        {
            SaveFileRecursiveData callbackData = new SaveFileRecursiveData() { ItemUrl = objUrl, DestPath = Path.Combine(destFolder, Path.GetFileName(objUrl)) };
            if (callback != null) callback(callbackData);

            try
            {
                SaveFileToDisk(objUrl, destFolder, credentials);
            }
            catch (Exception ex) { callbackData.Exception = ex; }

            callbackData.Complete = true;
            if (callback != null) callback(callbackData);
        }

        public static FileObj[] GetFileObj(string parentUrl, ICredentials credentials)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(parentUrl);
            request.Method = "PROPFIND";

            request.UserAgent = @"Microsoft-WebDAV-MiniRedir/6.1.7600";
            request.ContentLength = 0;
            request.AllowAutoRedirect = false;
            request.Headers.Add("Depth", "1");
            request.Headers.Add("translate", "f");
            request.Credentials = credentials == null ? CredentialCache.DefaultCredentials : credentials; ;

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                string responseFromServer = GetResponseContent(response);
                XDocument doc = XDocument.Parse(responseFromServer);

                return doc.Root.Elements()
                    .Select(el => new FileObj { ContentLength = int.Parse((el.Descendants().First(d => d.Name.LocalName == "getcontentlength").Value)), Url = el.Descendants().First(d => d.Name.LocalName == "href").Value, Name = el.Descendants().First(d => d.Name.LocalName == "displayname").Value, IsFolder = el.Descendants().FirstOrDefault(d => d.Name.LocalName == "isFolder") != null })
                    .Where(f => !string.IsNullOrEmpty(f.Name) && f.Url != parentUrl && (f.ContentLength > 0 || f.IsFolder))
                    .ToArray();
            }
        }

        public static void CreateSharePointFolder(string destUrl, ICredentials credentials)
        {
            WebRequest request = WebRequest.Create(destUrl);
            request.Method = "MKCOL";
            request.Headers.Add("Overwrite", "F");
            request.Credentials = credentials == null ? CredentialCache.DefaultCredentials : credentials;

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                string responseFromServer = GetResponseContent(response);
            }
        }

        private static void SafeStreamToFile(HttpWebResponse response, string fileName, string path)
        {
            const int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];

            using (Stream dataStream = response.GetResponseStream())
            {
                using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.Create, FileAccess.Write))
                {
                    for (; ; )
                    {
                        int read = dataStream.Read(buffer, 0, bufferSize);
                        if (read == 0) break;

                        fs.Write(buffer, 0, read);
                    }
                }
            }
        }

        private static string GetResponseContent(HttpWebResponse response)
        {
            using (Stream dataStream = response.GetResponseStream())
            {
                using (StreamReader reader = new StreamReader(dataStream))
                {
                    string responseFromServer = reader.ReadToEnd();
                    return responseFromServer;
                }
            }
        }

        private static void CopyStream(Stream inStream, Stream outStream)
        {
            byte[] buffer = new byte[1024];

            for (; ; )
            {
                int numBytesRead = inStream.Read(buffer, 0, buffer.Length);
                if (numBytesRead <= 0)
                    break;

                outStream.Write(buffer, 0, numBytesRead);
            }
        }
    }
}
